using System;
using System.Collections.Generic;
using System.Text;
using Decav.Geocoding;
using Decav.Gatsb.Data;
using Decav.Gatsb.Configuration;
using Decav.Diagnostics;
using Decav.Gatsb.Diagnostics;

namespace Decav.Gatsb.Application
{
    /// <summary>
    /// Executes searches for locations.
    /// </summary>
    public class LocationSearchAdapter
    {
        /// <summary>
        /// Finds a specific location based on a search criteria.
        /// </summary>
        /// <param name="criteria">The criteria to search on.</param>
        /// <returns>The locations found in the search.</returns>
        public virtual Location[] FindLocation(LocationSearchCriteria criteria)
        {
            if (criteria == null)
                throw new ArgumentNullException("criteria");

            Location[] dbLocations = FindNonGeocodedLocations(criteria); // Finds locations that are in the DB but not addresses or cross streets
            Location[] findLocations = GetGeocodedResults(criteria); // Finds locations that are geocoded...

            List<Location> items = new List<Location>(dbLocations);
            items.AddRange(findLocations);
            return items.ToArray();
        }

        private Location[] GetGeocodedResults(LocationSearchCriteria criteria)
        {
            string fullSearchString = null;
            #region Build Search
            if (criteria.PostalCode != null && criteria.PostalCode.Length > 0)
            {
                fullSearchString = string.Format("{0}, {1}", criteria.SearchString, criteria.PostalCode);
            }
            else if (criteria.State != null && criteria.State.Length > 0)
            {
                if (criteria.City != null && criteria.City.Length > 0)
                    fullSearchString = string.Format("{0}, {1}, {2}", criteria.SearchString, criteria.City, criteria.State);
                else
                    fullSearchString = string.Format("{0}, {1}", criteria.SearchString, criteria.State);
            }
            else if (criteria.City != null && criteria.City.Length > 0)
            {
                fullSearchString = string.Format("{0}, {1}", criteria.SearchString, criteria.City);
            }
            else
            {
                return new Location[] { };
            }
            #endregion

            try
            {
                if (criteria.Type == LocationType.CrossStreets)
                {
                    return FindIntersections(fullSearchString);
                }
                else if (criteria.Type == LocationType.StreetAddress)
                {
                    return FindAddresses(fullSearchString);
                }
            }
            catch (GeocodeSearchException)
            {
                 // Swallow this exception as it is a poorly structured query.
            }
            catch (Exception ex)
            {
                GatsbDiagnostics.WriteException(
                    string.Format("Search '{0}' failed when passed to the IGeocoder with type '{1}'.",
                    fullSearchString, criteria),
                    ex);

                // Write and swallow this exception as not to rely on outside services.
            }

            return new Location[] { };
        }

        private Location[] FindNonGeocodedLocations(LocationSearchCriteria criteria)
        {
            // This will not find addresses or intersections... these are found
            // by the geocoder.

            using (IGatsbDataAdapter adapt = GatsbApplication.Current.GetDataAdapter())
                return adapt.FindLocation(criteria);
        }

        private Location[] FindIntersections(string searchString)
        {
            IGeocoder geocoder = (IGeocoder)Activator.CreateInstance(Type.GetType(GatsbConfigurationSection.Current.GeocoderTypeName, false));
            GeocodeIntersectionResult[] results = geocoder.FindIntersection(searchString);

            List<Location> locations = new List<Location>();
            foreach (GeocodeIntersectionResult result in results)
                locations.Add(GetCreateGeocodedLocation(result));

            return locations.ToArray();
        }

        private Location[] FindAddresses(string searchString)
        {
            IGeocoder geocoder = (IGeocoder)Activator.CreateInstance(Type.GetType(GatsbConfigurationSection.Current.GeocoderTypeName, false));
            GeocodeAddressResult[] results = geocoder.FindAddress(searchString);

            List<Location> locations = new List<Location>();
            foreach (GeocodeAddressResult result in results)
                locations.Add(GetCreateGeocodedLocation(result));

            return locations.ToArray();
        }

        private Location GetCreateGeocodedLocation(GeocodeResult result)
        {
            using (IGatsbDataAdapter adapt = GatsbApplication.Current.GetDataAdapter())
            {
                if (result is GeocodeAddressResult)
                {
                    GeocodeAddressResult addr = (GeocodeAddressResult)result;

                    return adapt.GetGeocodedLocation(
                        string.Format("{0}, {1}", addr.Street, addr.City),
                        LocationType.StreetAddress, addr.Position,
                        addr.City, addr.State, addr.PostalCode,
                        addr.Street.ToString());
                }
                else
                {
                    GeocodeIntersectionResult ins = (GeocodeIntersectionResult)result;

                    return adapt.GetGeocodedLocation(
                        string.Format("{0} and {1}, {2}", ins.Street1, ins.Street2, ins.City),
                        LocationType.CrossStreets, ins.Position,
                        ins.City, ins.State, ins.PostalCode,
                        null);
                }
            }
        }
    }
}
